
#include <stdlib.h>
#include <string.h>
#include "stm32f10x_gpio.h"
#include "gpio.h"
#include "device.h"

extern int device_find_by_class_port(dev_class_e class, int port);

static int gpio_open(gpio_pin_e pin, gpio_cfg_t *config) {
    if(!config) return -1;

    GPIOMode_TypeDef mode;
    switch(config->mode) {
        case GPIO_MODE_AIN: mode = GPIO_Mode_AIN; break;
        case GPIO_MODE_IN_FLOATING: mode = GPIO_Mode_IN_FLOATING; break;
        case GPIO_MODE_IPD: mode = GPIO_Mode_IPD; break;
        case GPIO_MODE_IPU: mode = GPIO_Mode_IPU; break;
        case GPIO_MODE_OUT_OD: mode = GPIO_Mode_Out_OD; break;
        case GPIO_MODE_OUT_PP: mode = GPIO_Mode_Out_PP; break;
        case GPIO_MODE_AF_OD: mode = GPIO_Mode_AF_OD; break;
        case GPIO_MODE_AF_PP: mode = GPIO_Mode_AF_PP; break;
        default: return -1;
    }

    gpio_t *gpio = gpio_find_by_pin(pin);
    GPIO_TypeDef *GPIOx = (GPIO_TypeDef *)((gpio->pin / 16) * 0x0400 + 0x0800 + APB2PERIPH_BASE);
    uint16_t GPIO_Pin_x = 1 << (gpio->pin % 16);
    uint32_t RCC_APB2Periph_GPIOx;

    switch((int)GPIOx) {
        case (int)GPIOA: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOA; break;
        case (int)GPIOB: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOB; break;
        case (int)GPIOC: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOC; break;
        case (int)GPIOD: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOD; break;
        case (int)GPIOE: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOE; break;
        case (int)GPIOF: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOF; break;
        case (int)GPIOG: RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOG; break;
        default: return -1;
    }

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
	GPIO_InitStructure.GPIO_Mode = mode;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOx, &GPIO_InitStructure);

    memcpy(&gpio->cfg, config, sizeof(gpio_cfg_t));

    return 0;
}

static void gpio_write(gpio_pin_e pin, int level) {
    gpio_t *gpio = gpio_find_by_pin(pin);
    GPIO_TypeDef *GPIOx = (GPIO_TypeDef *)((gpio->pin / 16) * 0x0400 + 0x0800 + APB2PERIPH_BASE);
    uint16_t GPIO_Pin_x = 1 << (gpio->pin % 16);
    GPIO_WriteBit(GPIOx, GPIO_Pin_x, (BitAction)(!!level));
}

static int gpio_read(gpio_pin_e pin) {
    gpio_t *gpio = gpio_find_by_pin(pin);
    GPIO_TypeDef *GPIOx = (GPIO_TypeDef *)((gpio->pin / 16) * 0x0400 + 0x0800 + APB2PERIPH_BASE);
    uint16_t GPIO_Pin_x = 1 << (gpio->pin % 16);

    if(gpio->cfg.mode == GPIO_MODE_AIN
    || gpio->cfg.mode == GPIO_MODE_IN_FLOATING
    || gpio->cfg.mode == GPIO_MODE_IPD
    || gpio->cfg.mode == GPIO_MODE_IPU)
        return (int)GPIO_ReadInputDataBit(GPIOx, GPIO_Pin_x);
    else
        return (int)GPIO_ReadOutputDataBit(GPIOx, GPIO_Pin_x);
}

static void gpio_deinit(gpio_pin_e pin) {
    gpio_t *gpio = gpio_find_by_pin(pin);
    free(gpio);
}

#define GPIO_OPEN_FNC(e, v) int e##_OPEN (gpio_cfg_t *cfg) { \
    return gpio_open(e, cfg); \
}

#define GPIO_WRITE_FNC(e, v) void e##_WRITE (int level) { \
    gpio_write(e, level); \
}

#define GPIO_READ_FNC(e, v) int e##_READ (void) { \
    return gpio_read(e); \
}

#define GPIO_DEINIT_FNC(e, v) void e##_DEINIT (void) { \
    gpio_deinit(e); \
}

GPIO_PIN_LIST(GPIO_OPEN_FNC)
GPIO_PIN_LIST(GPIO_WRITE_FNC)
GPIO_PIN_LIST(GPIO_READ_FNC)
GPIO_PIN_LIST(GPIO_DEINIT_FNC)

#define GPIO_METHOD_PROC(e, v) case e: { \
    gpio->open = e##_OPEN; \
    gpio->write = e##_WRITE; \
    gpio->read = e##_READ; \
    gpio->deinit = e##_DEINIT; \
    break; \
}

gpio_t *gpio_init(gpio_pin_e pin) {
    if(pin >= GPIO_PIN_MAX) return NULL;

	gpio_t *gpio = malloc(sizeof(gpio_t));
	if(!gpio) return NULL;

	memset(gpio, 0, sizeof(gpio_t));

	gpio->pin = pin;

    switch(pin) {
        GPIO_PIN_LIST(GPIO_METHOD_PROC)
        default: break;
    }

    return gpio;
}

gpio_t *gpio_find_by_pin(gpio_pin_e pin) {
    return (gpio_t *)device_find_by_class_port(DEV_CLASS_GPIO, (int)pin);
}
